---
title: "Zod 3 to 4 Migration"
---

Zod 4 introduces a cleaner API, improved TypeScript inference, and several breaking changes aimed at making schema validation both stricter *and* more ergonomic.

Key highlights include:

1. **Unified Object Behaviour** – `.passthrough()` replaces `.nonstrict()` and better aligns with Zod’s philosophy of explicitness.
2. **Enhanced Metadata APIs** – `meta()` supersedes several ad-hoc helpers such as `.describe()`.
3. **Error Handling Revamp** – new `ZodError` export path and richer error objects.
4. **Union & Intersection Updates** – explicit helpers (`z.union`, `z.intersection`) over chained `.or()` / `.and()` calls.
5. **Ecosystem Clean-up** – deprecated utilities removed, smaller bundle size, faster parse times.

For a full changelog see the [Zod v4 release notes](https://github.com/colinhacks/zod/releases/tag/v4.0.1).

Codemod provides an automated Zod 3 → 4 upgrade experience. This page walks you through running the codemod and polishing the remaining edge-cases.

## Getting started

<CardGroup cols={2}>
<Card
    title="Zod 4 Release Notes"
    icon="book-open"
    href="https://github.com/colinhacks/zod/releases/tag/v4.0.1"
/>
<Card
    title="Zod 4 Migration Recipe"
    icon="cauldron"
    href="https://app.codemod.com/registry/zod-3-4"
/>
</CardGroup>

## Migration Steps
<Steps>
    <Step title="Install Zod 4">
        Install the latest major version of Zod. We recommend using **exact** versions to avoid surprises:
        ```bash
        npm install --save-exact zod@^4
        # or
        yarn add --exact zod@^4
        ```
        Zod has no peer dependencies, so that’s the only package you need to bump.
    </Step>

    <Step title="Run the codemod">
        Launch the codemod recipe to automatically rewrite common breaking changes:

        ```bash
        npx codemod jssg run zod-3-4
        ```

        <Tip>
            **Can you rollback?**

            Git is your friend—commit before you run the codemod. The Codemod CLI also supports the [`--dry-run` flag](https://docs.codemod.com/cli/cli-reference#param-dry-run) which lets you preview the changes without applying them.
        </Tip>

        View a list of available CLI commands & options [here ->](https://docs.codemod.com/cli/cli-reference)

        #### What the recipe changes

        The migration recipe **only** targets Zod-specific breaking changes. It will *not* touch unrelated code patterns like `var` declarations or debug `console.log` statements. Concretely, it performs the following refactors:

        - `.nonstrict()` → `.passthrough()`
        - Chained `.or()` / `.and()` calls → `z.union([...])` / `z.intersection([...])`
        - `.describe("…")` → `.meta({ description: "…" })`
        - Updates `ZodError`, `z.ZodSchema` import paths to the new `zod/v4` entrypoints
        - Removes helpers removed in v4 (`z.lazyobject`, legacy refinements, etc.)

        If you see documentation elsewhere mentioning generic tasks such as *“modernizing syntax patterns”* or *“removing debug statements”*, those refer to different codemods—not this Zod migration.
    </Step>

    <Step title="Review & test">
        The codemod handles *most* changes, but some APIs require manual tweaks. We recommend:

        1. **Type-check** the project:
           ```bash
           pnpm tsc --noEmit
           ```
        2. **Run your test suite** and fix any remaining failures.
        3. Search for `TODO(zod-4-migration)` comments the codemod leaves behind for ambiguous cases.
    </Step>

    <Step title="Before & After examples">
        Below is a non-exhaustive diff generated by the codemod:

        ```tsx diff
        -import { z, ZodError } from "zod";
        +import { z } from "zod/v4";
        +import { ZodError } from "zod/v4/core";

        -const value = z.literal('yes').or(z.literal('no'));
        +const value = z.union([
        +  z.literal('yes'),
        +  z.literal('no'),
        +]);

        -const password = z.string().min(8).describe("User's password");
        +const password = z.string().min(8).meta({ description: "User's password" });
        ```
    </Step>
</Steps>

## Tested versions

| From | To | Languages |
| --- | --- | --- |
| `zod@3.22.x` | `zod@4.0.0` | JavaScript & TypeScript |

## FAQ

### Does the codemod cover *every* breaking change?
No. It focuses on the highest-impact, mechanical updates. Review the Zod release notes for edge-cases such as custom `.refine()` logic or plugin APIs.

### My project uses Babel / SWC – is that supported?
Yes. The codemod operates on the output AST and is agnostic of the underlying compiler.
